package net.lastcoder.ifserver.server;

import net.lastcoder.ifserver.Globals;
import net.lastcoder.ifserver.util.NamedThreadFactory;

import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.executor.ExecutorFilter;
import org.apache.mina.transport.socket.SocketSessionConfig;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * auth: WenYF
 * date: 2016/11/23
 */
public class ServerConnectionAcceptor {
    private static final Logger Log = LoggerFactory.getLogger(ServerConnectionAcceptor.class);

    private final ServerHandler terminateHandler;

    private NioSocketAcceptor socketAcceptor;
    private final String name;
    private int port;


    public ServerConnectionAcceptor() {
        terminateHandler = new ServerHandler();

        name = Globals.getXMLProperty("terminate.acceptor.name", "terminate server");
    }

    public synchronized void start() {
        if (socketAcceptor != null) {
            Log.warn("Unable to start acceptor (it is already started!)");
            return;
        }

        String interfaceName = Globals.getXMLProperty("terminate.network.interface", "0.0.0.0");
        port = Globals.getXMLProperty("terminate.network.port", 9588);
        try {
            // Configure the thread pool that is to be used.
            final int initialSize = Globals.getXMLProperty("terminate.acceptor.initThreadCount", 2);
            final ExecutorFilter executorFilter = new ExecutorFilter(initialSize
                    , Globals.getXMLProperty("terminate.acceptor.maxThreadCount", 5)
                    , 60, TimeUnit.SECONDS);
            final ThreadPoolExecutor eventExecutor = (ThreadPoolExecutor)
                    executorFilter.getExecutor();
            final ThreadFactory threadFactory = new NamedThreadFactory(name + "-thread-"
                    , eventExecutor.getThreadFactory(), true, null);
            eventExecutor.setThreadFactory(threadFactory);

            // Construct a new socket acceptor, and configure it.
            socketAcceptor = buildSocketAcceptor();

            final DefaultIoFilterChainBuilder filterChain = socketAcceptor.getFilterChain();
            filterChain.addFirst("threadModel", executorFilter);
            filterChain.addLast("codec", new ProtocolCodecFilter(new ServerCodecFactory()));

            int size = Globals.getXMLProperty("terminate.acceptor.maxReadBufferSize", 4096);
            socketAcceptor.getSessionConfig().setMaxReadBufferSize(size);
            Log.debug("Throttling read buffer for connections to max={} bytes", size);

            // Start accepting connections
            socketAcceptor.setHandler(terminateHandler);

            socketAcceptor.bind(new InetSocketAddress(interfaceName, port));
        } catch (Exception e) {
            Log.error("Error starting: " + interfaceName + ":" + port, e);
            // Reset for future use.
            if (socketAcceptor != null) {
                try {
                    socketAcceptor.unbind();
                } finally {
                    socketAcceptor = null;
                }
            }
        }
    }

    public synchronized void stop() {
        if (socketAcceptor != null) {
            socketAcceptor.unbind();
            socketAcceptor = null;
        }
    }

    public boolean isIdle() {
        return this.socketAcceptor != null && this.socketAcceptor.getManagedSessionCount() == 0;
    }

    public synchronized int getPort() {
        return port;
    }

    // TODO see if we can avoid exposing MINA internals.
    public synchronized NioSocketAcceptor getSocketAcceptor() {
        return socketAcceptor;
    }

    private static NioSocketAcceptor buildSocketAcceptor() {
        // Create SocketAcceptor with correct number of processors
        final int processorCount = Globals.getXMLProperty("terminate.acceptor.processorCount"
                , Runtime.getRuntime().availableProcessors());

        final NioSocketAcceptor socketAcceptor = new NioSocketAcceptor(processorCount);

        // Set that it will be possible to bind a socket if there is a connection in the timeout state.
        socketAcceptor.setReuseAddress(true);

        // Set the listen backlog (queue) length. Default is 50.
        socketAcceptor.setBacklog(Globals.getXMLProperty("terminate.acceptor.socket.backlog", 50));

        // Set default (low level) settings for new socket connections
        final SocketSessionConfig socketSessionConfig = socketAcceptor.getSessionConfig();

        //socketSessionConfig.setKeepAlive();
        final int receiveBuffer = Globals.getXMLProperty("terminate.acceptor.socket.buffer.receive", -1);
        if (receiveBuffer > 0) {
            socketSessionConfig.setReceiveBufferSize(receiveBuffer);
        }

        final int sendBuffer = Globals.getXMLProperty("terminate.acceptor.socket.buffer.send", -1);
        if (sendBuffer > 0) {
            socketSessionConfig.setSendBufferSize(sendBuffer);
        }

        final int linger = Globals.getXMLProperty("terminate.acceptor.socket.linger", -1);
        if (linger > 0) {
            socketSessionConfig.setSoLinger(linger);
        }

        socketSessionConfig.setTcpNoDelay(Globals.getXMLProperty("terminate.acceptor.socket.tcp-nodelay"
                , socketSessionConfig.isTcpNoDelay()));

        return socketAcceptor;
    }
}
